﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides methods and structures to describe a mathematical sedenion.
    /// </summary>
    [Serializable]
    public class Sedenion : ISedenion
    {
        /// <summary>
        /// The real number part of the sedenion.
        /// </summary>
        private double realPart;

        /// <summary>
        /// The imaginary number part of the sedenion.
        /// </summary>
        private double[] imaginaryNumberParts;

        /// <summary>
        /// Initializes a new instance of the <see cref="Sedenion"/> class.
        /// </summary>
        public Sedenion()
        {
            this.imaginaryNumberParts = new double[15];

            for (int i = 0; i < this.imaginaryNumberParts.Length; i++)
            {
                this.imaginaryNumberParts[i] = 0;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Sedenion"/> class.
        /// </summary>
        /// <param name="realPart">The real number part of the sedenion.</param>
        public Sedenion(double realPart)
        {
            this.realPart = realPart;
            this.imaginaryNumberParts = new double[15];

            for (int i = 0; i < this.imaginaryNumberParts.Length; i++)
            {
                this.imaginaryNumberParts[i] = 0;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Sedenion"/> class.
        /// </summary>
        /// <param name="realPart">The real number part of the sedenion.</param>
        /// <param name="e1">The e1 part of the sedenion.</param>
        /// <param name="e2">The e2 part of the sedenion.</param>
        /// <param name="e3">The e3 part of the sedenion.</param>
        /// <param name="e4">The e4 part of the sedenion.</param>
        /// <param name="e5">The e5 part of the sedenion.</param>
        /// <param name="e6">The e6 part of the sedenion.</param>
        /// <param name="e7">The e7 part of the sedenion.</param>
        /// <param name="e8">The e8 part of the sedenion.</param>
        /// <param name="e9">The e9 part of the sedenion.</param>
        /// <param name="e10">The e10 part of the sedenion.</param>
        /// <param name="e11">The e11 part of the sedenion.</param>
        /// <param name="e12">The e12 part of the sedenion.</param>
        /// <param name="e13">The e13 part of the sedenion.</param>
        /// <param name="e14">The e14 part of the sedenion.</param>
        /// <param name="e15">The e15 part of the sedenion.</param>
        public Sedenion(double realPart, double e1, double e2, double e3, double e4,
                        double e5, double e6, double e7, double e8, double e9, double e10,
                        double e11, double e12, double e13, double e14, double e15)
        {
            this.realPart = realPart;
            this.imaginaryNumberParts = new double[15];
            this.imaginaryNumberParts[0] = e1;
            this.imaginaryNumberParts[1] = e2;
            this.imaginaryNumberParts[2] = e3;
            this.imaginaryNumberParts[3] = e4;
            this.imaginaryNumberParts[4] = e5;
            this.imaginaryNumberParts[5] = e6;
            this.imaginaryNumberParts[6] = e7;
            this.imaginaryNumberParts[7] = e8;
            this.imaginaryNumberParts[8] = e9;
            this.imaginaryNumberParts[9] = e10;
            this.imaginaryNumberParts[10] = e11;
            this.imaginaryNumberParts[11] = e12;
            this.imaginaryNumberParts[12] = e13;
            this.imaginaryNumberParts[13] = e14;
            this.imaginaryNumberParts[14] = e15;
        }

        /// <summary>
        /// Gets or sets the real number part of the sedenion.
        /// </summary>
        /// <value>The real number part of the sedenion.</value>
        public double RealPart
        {
            get { return realPart; }
            set { realPart = value; }
        }

        /// <summary>
        /// Gets or sets the imaginary number parts in form of an array.
        /// </summary>
        /// <value>The imaginary number parts in form of an array.</value>
        public double[] ImaginaryNumberParts
        {
            get { return imaginaryNumberParts; }
            set { imaginaryNumberParts = value; }
        }

        /// <summary>
        /// Gets or sets the e1 part of the sedenion.
        /// </summary>
        /// <value>The e1 part of the sedenion.</value>
        public double E1
        {
            get { return this.imaginaryNumberParts[0]; }
            set { this.imaginaryNumberParts[0] = value; }
        }

        /// <summary>
        /// Gets or sets the e2 part of the sedenion.
        /// </summary>
        /// <value>The e2 part of the sedenion.</value>
        public double E2
        {
            get { return this.imaginaryNumberParts[1]; }
            set { this.imaginaryNumberParts[1] = value; }
        }

        /// <summary>
        /// Gets or sets the e3 part of the sedenion.
        /// </summary>
        /// <value>The e3 part of the sedenion.</value>
        public double E3
        {
            get { return this.imaginaryNumberParts[2]; }
            set { this.imaginaryNumberParts[2] = value; }
        }

        /// <summary>
        /// Gets or sets the e4 part of the sedenion.
        /// </summary>
        /// <value>The e4 part of the sedenion.</value>
        public double E4
        {
            get { return this.imaginaryNumberParts[3]; }
            set { this.imaginaryNumberParts[3] = value; }
        }

        /// <summary>
        /// Gets or sets the e5 part of the sedenion.
        /// </summary>
        /// <value>The e5 part of the sedenion.</value>
        public double E5
        {
            get { return this.imaginaryNumberParts[4]; }
            set { this.imaginaryNumberParts[4] = value; }
        }

        /// <summary>
        /// Gets or sets the e6 part of the sedenion.
        /// </summary>
        /// <value>The e6 part of the sedenion.</value>
        public double E6
        {
            get { return this.imaginaryNumberParts[5]; }
            set { this.imaginaryNumberParts[5] = value; }
        }

        /// <summary>
        /// Gets or sets the e7 part of the sedenion.
        /// </summary>
        /// <value>The e7 part of the sedenion.</value>
        public double E7
        {
            get { return this.imaginaryNumberParts[6]; }
            set { this.imaginaryNumberParts[6] = value; }
        }

        /// <summary>
        /// Gets or sets the e8 part of the sedenion.
        /// </summary>
        /// <value>The e8 part of the sedenion.</value>
        public double E8
        {
            get { return this.imaginaryNumberParts[7]; }
            set { this.imaginaryNumberParts[7] = value; }
        }

        /// <summary>
        /// Gets or sets the e9 part of the sedenion.
        /// </summary>
        /// <value>The e9 part of the sedenion.</value>
        public double E9
        {
            get { return this.imaginaryNumberParts[8]; }
            set { this.imaginaryNumberParts[8] = value; }
        }

        /// <summary>
        /// Gets or sets the e10 part of the sedenion.
        /// </summary>
        /// <value>The e10 part of the sedenion.</value>
        public double E10
        {
            get { return this.imaginaryNumberParts[9]; }
            set { this.imaginaryNumberParts[9] = value; }
        }

        /// <summary>
        /// Gets or sets the e11 part of the sedenion.
        /// </summary>
        /// <value>The e11 part of the sedenion.</value>
        public double E11
        {
            get { return this.imaginaryNumberParts[10]; }
            set { this.imaginaryNumberParts[10] = value; }
        }

        /// <summary>
        /// Gets or sets the e12 part of the sedenion.
        /// </summary>
        /// <value>The e12 part of the sedenion.</value>
        public double E12
        {
            get { return this.imaginaryNumberParts[11]; }
            set { this.imaginaryNumberParts[11] = value; }
        }

        /// <summary>
        /// Gets or sets the e13 part of the sedenion.
        /// </summary>
        /// <value>The e13 part of the sedenion.</value>
        public double E13
        {
            get { return this.imaginaryNumberParts[12]; }
            set { this.imaginaryNumberParts[12] = value; }
        }

        /// <summary>
        /// Gets or sets the e14 part of the sedenion.
        /// </summary>
        /// <value>The e14 part of the sedenion.</value>
        public double E14
        {
            get { return this.imaginaryNumberParts[13]; }
            set { this.imaginaryNumberParts[13] = value; }
        }

        /// <summary>
        /// Gets or sets the e15 part of the sedenion.
        /// </summary>
        /// <value>The e15 part of the sedenion.</value>
        public double E15
        {
            get { return this.imaginaryNumberParts[14]; }
            set { this.imaginaryNumberParts[14] = value; }
        }

        /// <summary>
        /// Copies the current instance of an sedenion.
        /// </summary>
        /// <returns>The copy of the current instance.</returns>
        public Sedenion Copy()
        {
            return new Sedenion(this.realPart, this.imaginaryNumberParts[0], this.imaginaryNumberParts[1],
                                this.imaginaryNumberParts[2], this.imaginaryNumberParts[3], this.imaginaryNumberParts[4],
                                this.imaginaryNumberParts[5], this.imaginaryNumberParts[6], this.imaginaryNumberParts[7],
                                this.imaginaryNumberParts[8], this.imaginaryNumberParts[9],
                                this.imaginaryNumberParts[10],
                                this.imaginaryNumberParts[11], this.imaginaryNumberParts[12],
                                this.imaginaryNumberParts[13],
                                this.imaginaryNumberParts[14]);
        }

        /// <summary>
        /// Compares the current instance of a sedenion to another instance.
        /// </summary>
        /// <param name="compare">The sedenion to compare.</param>
        /// <returns>True if both sedenions are even otherwise, false.</returns>
        public bool CompareTo(Sedenion compare)
        {
            return this == compare;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">The first sedenion.</param>
        /// <param name="b">The second sedenion.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(Sedenion a, Sedenion b)
        {
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            if (((object) a == null) || ((object) b == null))
            {
                return false;
            }

            for (int i = 0; i < a.ImaginaryNumberParts.Length; i++)
            {
                if (a.ImaginaryNumberParts[i] != b.ImaginaryNumberParts[i])
                {
                    return false;
                }
            }

            return a.RealPart == b.RealPart;
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">The first sedenion.</param>
        /// <param name="b">The second sedenion.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(Sedenion a, Sedenion b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The first sedenion.</param>
        /// <param name="b">The second sedenion.</param>
        /// <returns>The result of the operator.</returns>
        public static Sedenion operator +(Sedenion a, Sedenion b)
        {
            if (a == (Sedenion) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (Sedenion) null)
            {
                throw new ArgumentNullException("b");
            }

            Sedenion result = new Sedenion(a.RealPart + b.RealPart);

            for (int i = 0; i < a.ImaginaryNumberParts.Length; i++)
            {
                result.ImaginaryNumberParts[i] = a.ImaginaryNumberParts[i] + b.ImaginaryNumberParts[i];
            }

            return result;
        }

        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="a">The first sedenion.</param>
        /// <param name="b">The second sedenion.</param>
        /// <returns>The result of the operator.</returns>
        public static Sedenion operator -(Sedenion a, Sedenion b)
        {
            if (a == (Sedenion) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (Sedenion) null)
            {
                throw new ArgumentNullException("b");
            }

            Sedenion result = new Sedenion(a.RealPart - b.RealPart);

            for (int i = 0; i < a.ImaginaryNumberParts.Length; i++)
            {
                result.ImaginaryNumberParts[i] = a.ImaginaryNumberParts[i] - b.ImaginaryNumberParts[i];
            }

            return result;
        }

        /// <summary>
        /// Implements the operator *.
        /// </summary>
        /// <param name="a">The sedenion for the multiplication.</param>
        /// <param name="value">The value to multiply with.</param>
        /// <returns>The result of the operator.</returns>
        public static Sedenion operator *(Sedenion a, double value)
        {
            if (a == (Sedenion) null)
            {
                throw new ArgumentNullException("a");
            }

            Sedenion result = new Sedenion(a.RealPart * value);

            for (int i = 0; i < a.ImaginaryNumberParts.Length; i++)
            {
                result.ImaginaryNumberParts[i] = a.ImaginaryNumberParts[i] * value;
            }

            return result;
        }

        /// <summary>
        /// Powers the current sedenion raised to the specified power.
        /// </summary>
        /// <param name="value">The value to raised with.</param>
        /// <returns>The new sedenion raised to the specified power.</returns>
        public Sedenion Pow(double value)
        {
            Sedenion result = new Sedenion(Math.Pow(this.realPart, value));

            for (int i = 0; i < this.imaginaryNumberParts.Length; i++)
            {
                result.ImaginaryNumberParts[i] = Math.Pow(this.imaginaryNumberParts[i], value);
            }

            return result;
        }

        /// <summary>
        /// Formats a number for the ToString method.
        /// </summary>
        /// <param name="number">The number to format.</param>
        /// <returns>The formatted number.</returns>
        private static String FormatNumber(double number)
        {
            if (number < 0)
            {
                return " - " + (-number).ToString();
            }

            return " + " + number.ToString();
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            String result = this.realPart.ToString();

            for (int i = 0; i < this.imaginaryNumberParts.Length; i++)
            {
                result += FormatNumber(this.imaginaryNumberParts[i]) + "e" + (i + 1);
            }

            return result;
        }
    }
}